<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * 由于博主对IP的质量的要求偏低，故可以用此方法解封，关键词抓取不可以
 * @author zengjie
 *
 */
class Unlock_bozhu_ip extends CI_Controller {
	/**
	* php_bin
	*
	* @var string
	*/
	private $_php_bin = '/usr/local/php/bin/php';
	private $_cookie;

	/**
	* __construct
	*
	* @access public
	* @return string $mixed
	*/
	public function __construct() {
        parent::__construct();
		ini_set("display_errors","on");
		error_reporting(E_ALL & ~E_NOTICE);
		set_time_limit(0);
	}



	/**
	*  主进程持续执行2分钟..到期后结束中断 中间只检查需要解封的IP.并启动子进程解封
	* 1 获取需要解封的IP列表
	* 2 遍历 需解封IP列表,
	* 3 检查系统中现有可用IP数..如果可用数超过5个..则不需要再解封..以节省解码接口请求次数
	* 4 检查当前IP的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
	* 5 检查当前IP是否已有其它进程在解封.如果有.则continue
	* 6 将当前ip标识正在解封..并启动子进程开始解封..
	* 7 子进程执行完毕(不论是否成功与否).将解除此ip正在解封标识
	*
	*
	* 注意第4步
	* 此处时间不能用配置文件中的 adsl_life,adsl_life的意义 是与抓取时 curl_timeout 相对应的,为了解决IP正在用.而机器已拨号的问题，比如场景：
	* 一次请求需要20s.如果我们取IP的时候.不做限定.那有可能取到已经过了115s的IP.这样的话因此机器马上就会换IP..注定此次请求会出错..增加失败率
	*
	* @access public
	* @return string $mixed
	*/
	public function multi_start() {
		//每次抓取请求最长可用时间
		$crawl_curl_timeout = $this->config->item('curl_timeout');
		$crawl_curl_timeout = $crawl_curl_timeout - 10;


		//可用ip数  当超过此数值..系统将不再解封IP
		$usable_ip_num = $this->config->item('usable_ip_num');

		$this->load->model('universal/mobility_ip_model');

		$start_time = $curr_time = time();
		$listing_end_time = time()+120;

		while ($curr_time < $listing_end_time){
			//1 获取需要解封的IP列表
			$locked_ip_list = array();
			if(false == ($locked_ip_list = $this->mobility_ip_model->get_locked_bozhu_list('sina'))){
				echo "get_locked_bozhu_list null, sleep \n";
				sleep(1);
				continue;
			}

			//2 遍历 需解封IP列表,
			foreach ($locked_ip_list as $ip_info) {
				//3 检查系统中现有可用IP数..如果可用数超过4个..则不需要再解封..以节省解码接口请求次数
				//if($usable_ip_num < ($usable_num = $this->mobility_ip_model->get_normal_count('sina'))){
					//break;
				//}

				//如果此IP的入库时间距现在.现过了110s..则说明马上会重新拨号..所以没有必要解封
				//if((time()-$ip_info['ctime']) >= 110) continue;

				//4 此处是为了解决:当ip的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
				//if((120 - (time()-$ip_info['ctime'])) < $crawl_curl_timeout) continue;

				//5 检查当前IP是否已有其它进程在解封.如果有.则continue
				if($this->is_unlocking($ip_info['source'])) continue;

				//6 将当前ip标识正在解封..并启动子进程开始解封..
				$this->add_unlocking($ip_info['source']);

				$file = "/home/data1/crawl/public/index.php unlock_bozhu_ip unlock {$ip_info['id']} {$ip_info['ip']}:{$ip_info['port']} {$ip_info['source']}";
				$command = $this->_php_bin.' '.$file.' >> /root/unlock.log &';
				echo $command."\n";
				exec($command);
			}

			//防止重复解锁
			sleep(3);

			//记录当前时间.
			$curr_time = time();
		}

	}



	/**
	* 单进程解封所有被封IP
	*
	* 注意
	* 此处时间不能用配置文件中的 adsl_life,adsl_life的意义 是与抓取时 curl_timeout 相对应的,为了解决IP正在用.而机器已拨号的问题，比如场景：
	* 一次请求需要20s.如果我们取IP的时候.不做限定.那有可能取到已经过了115s的IP.这样的话因此机器马上就会换IP..注定此次请求会出错..增加失败率
	*
	* @access public
	* @return string $mixed
	*/
	public function start() {
		//每次抓取请求最长可用时间
		$crawl_curl_timeout = $this->config->item('curl_timeout');

		$this->load->model('universal/mobility_ip_model');
		$locked_ip_list = $this->mobility_ip_model->get_locked_bozhu_list('sina');
		//var_dump($locked_ip_list);

		foreach ($locked_ip_list as $ip_info) {
			//如果此IP的入库时间距现在.现过了110s..则说明马上会重新拨号..所以没有必要解封
			//if((time()-$ip_info['ctime']) >= $this->_keeplive_time) continue;

			//此处是为了解决:当ip的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
			//if((120 - (time()-$ip_info['ctime'])) < $crawl_curl_timeout) continue;

			$ip = "{$ip_info['ip']}:{$ip_info['port']}";
			$this->unlock($ip_info['id'],$ip,$ip_info['source']);
		}
	}
	
	/**
	* unlock
	* 1 获取验证码图片
	* 		1.a 获取验证码内容出错,可能会是因为adsl服务器ip已变更/或其它网络原因,则不再请求uu接口,不请求解封..只解除正在解封标识,记录日志
	* 2 用拿到的验证码图片内容,换取 验证码的值
	*		2.a 获取验证码出错,可能是因为接口没费用/或网络原因失败..则不再请求解封..只解除正在解封标识,记录日志
	* 3 能正确拿到验证码，则请求解封...
	* 4 如果 post 解封请求失败..则 .只解除正在解封标识,记录日志
	* 5 如果返回的code不是标识成功的..只解除正在解封标识,记录日志
	* 6 如果解封返回的code标识成功..则更新ip状态...
	*
	* @access public
	* @param array $ip_info  ip信息,格式如：array('id'=>1203433,'ip'=>'223.241.161.28','port'=>'20707','source'=>'1') [Optional]
	* @return string $mixed
	*/
	public function unlock($id,$ip,$source) {
		$ip_info['id'] = $id;
		$ip_info['source'] = $source;
		list($ip_info['ip'],$ip_info['port']) = explode(':',$ip);

		//$ip_info = array('id'=>1206165,'ip'=>'223.241.161.103','port'=>'20707','source'=>'1');
		if(empty($ip_info['id']) || empty($ip_info['source']) || empty($ip_info['ip']) || empty($ip_info['port'])){
			echo "unlock param is error !! ip_info_string:".json_encode($ip_info)."\n\n";

			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return false;
		}

		$log_data['start_time'] = time();
		$log_data['source'] = $ip_info['source'];
		$log_data['ip'] = $ip_info['ip'];
		$log_data['port'] = $ip_info['port'];
		$this->load->model('universal/unlock_log_model');


		$proxy = "{$ip_info['ip']}:{$ip_info['port']}";
		
		$rs = $this->send_http($proxy);
		if(empty($rs)){
			//解封失败
			echo "+++{$proxy}+++unlocking error \n";
			$this->del_unlocking($ip_info['source']);//删除正在解封标识
		}else{
			echo "+++{$proxy}+++unlocking success \n";
			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			
			$this->load->model('universal/mobility_ip_model');
			$this->mobility_ip_model->update_status($ip_info['id'],'sina');
		}
	}
	
	function send_http($ip = "") {
	
		$url = "http://m.weibo.cn/container/getIndex?type=uid&containerid=1076031000104012&page=1";
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
		curl_setopt($ch, CURLOPT_TIMEOUT, 15);
	
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		
		if(!empty($ip)) {
			curl_setopt($ch, CURLOPT_PROXY, $ip);
			curl_setopt($ch, CURLOPT_PROXYTYPE , CURLPROXY_SOCKS5);
			curl_setopt($ch, CURLOPT_PROXYUSERPWD, "hongmai:soften.cn");
		}
	
		$rs = curl_exec($ch);
		$http_info = curl_getinfo($ch);
	
		curl_close($ch);
		if($http_info['http_code'] != 200 && $http_info['http_code'] != 302 ){
			return false;
		}
		return $rs;
	}


	/**
	* 检查是否正在执行解锁
	* 此处不需记录 ip,因为即使是同一个机器的老IP正在解锁.又产生了新IP,且也被封了, 只有等老IP解封完了..会自动从内存中清除..才会有新进程来给新IP解封..
	*
	* 将最后解锁时间引入进来,是为了解决:
	* 当多进程执行的时候..有些后执行的进程会将已完成解封的 source再保存进来..这样就会导致.此机器一直被标识正在解封.就会一直无法解封.
	* 当出现以上情况时.引入最后解封时间..如果此机器的已标识正在解封..但最后解封时间是10分钟前的...则认为此IP没有标识正在解封..
	*
	* 如果正在解锁返回true
	* 否则返回false
	* 当入参错误时.会抛异常
	*
	* @access public
	* @param int $source 需要检查的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function is_unlocking($source) {
		if(empty($source)){
			throw new Exception('is_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));
		if(false == ($unlocking_time = $this->cache->get($cache_key))){
			return false;
		}

		$sometime_ago = time()- 300;//5分钟前
		if($unlocking_time < $sometime_ago){//最后解封时间是5分钟前的...则认为此IP没有标识正在解封..
			//echo '2222';
			return false;
		}else{
			//echo '33333';
			return true;
		}

	}


	/**
	* 将指定source的机器标注为正在执行解锁
	*
	* @access public
	* @param int $source 需要加锁的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function add_unlocking($source) {
		if(empty($source)){
			throw new Exception('add_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));


		return $unlocking_info_string = $this->cache->save($cache_key,time(),0);
	}


	/**
	* 解除指定source的机器  正在解锁标注
	*
	* @access public
	* @param int $source 需要解锁的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function del_unlocking($source) {
		if(empty($source)){
			throw new Exception('del_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));

		return $this->cache->delete($cache_key);
	}
}
